/*
 * PtokaX - hub server for Direct Connect peer to peer network.

 * Copyright (C) 2002-2005  Ptaczek, Ptaczek at PtokaX dot org
 * Copyright (C) 2004-2015  Petr Kozelka, PPK at PtokaX dot org

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3
 * as published by the Free Software Foundation.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

//---------------------------------------------------------------------------
#include "stdinc.h"
//---------------------------------------------------------------------------
#include "LuaInc.h"
//---------------------------------------------------------------------------
#include "LanguageManager.h"
#include "ServerManager.h"
#include "serviceLoop.h"
#include "SettingManager.h"
#include "utility.h"
//---------------------------------------------------------------------------
#pragma hdrstop
//---------------------------------------------------------------------------
#include "ExceptionHandling.h"
#include "LuaScript.h"
#ifndef _DEBUG
#include "DbgHelp.h"
#include "../doctor-dump/CrashRpt.h"

template<typename T> static T getFilePath(const T& path)
{
	const auto i = path.rfind('\\');
	return (i != string_t::npos) ? path.substr(0, i + 1) : path;
}
static void crash_test_doctor_dump()
{
#ifndef _DEBUG
	*((int*)0) = 0;
#endif
}

crash_rpt::ApplicationInfo* GetApplicationInfo()
{
	static crash_rpt::ApplicationInfo appInfo;
	appInfo.ApplicationInfoSize = sizeof(appInfo);
	appInfo.ApplicationGUID = "11B2CC9B-B1E9-4894-9AE6-6C4CB3DFDECA";

	    
#ifdef _WIN64
	appInfo.Prefix = "ptokax-console-x64";             // Prefix that will be used with the dump name: YourPrefix_v1.v2.v3.v4_YYYYMMDD_HHMMSS.mini.dmp.
	appInfo.AppName = L"PtokaX++ console x64";         // Application name that will be used in message box.
#else
	appInfo.Prefix = "ptokax-console-x86";             // Prefix that will be used with the dump name: YourPrefix_v1.v2.v3.v4_YYYYMMDD_HHMMSS.mini.dmp.
	appInfo.AppName = L"PtokaX++ console x86";             // Application name that will be used in message box.
#endif
	appInfo.Company = L"PtokaX++ developers";  // Company name that will be used in message box.
	appInfo.V[0] = 0;
	appInfo.V[1] = 5;
	appInfo.V[2] = 1;
	appInfo.V[3] = USHORT(atoi(BUILD_NUMBER));
	return &appInfo;
}

crash_rpt::HandlerSettings* GetHandlerSettings()
{
	static wchar_t g_path_sender[MAX_PATH] = {0};
	static wchar_t g_path_dbhelp[MAX_PATH] = {0};
	::GetModuleFileNameW(NULL, g_path_sender, MAX_PATH);
	wcscpy(g_path_dbhelp, g_path_sender);
	auto l_tslash = wcsrchr(g_path_sender, '\\');
	if (l_tslash)
	{
		l_tslash++;
#ifdef _WIN64
		wcscpy(l_tslash, L"sendrpt-x64.exe");
#else
		wcscpy(l_tslash, L"sendrpt-x86.exe");
#endif
		l_tslash = wcsrchr(g_path_dbhelp, '\\');
		l_tslash++;
#ifdef _WIN64
		wcscpy(l_tslash, L"dbghelp-x64.dll");
#else
		wcscpy(l_tslash, L"dbghelp-x86.dll");
#endif
	}
	static crash_rpt::HandlerSettings g_handlerSettings;
	g_handlerSettings.HandlerSettingsSize = sizeof(g_handlerSettings);
	g_handlerSettings.OpenProblemInBrowser = TRUE;
	g_handlerSettings.SendRptPath = g_path_sender;
	g_handlerSettings.DbgHelpPath = g_path_dbhelp;
	return &g_handlerSettings;
}

crash_rpt::CrashRpt g_crashRpt(
#ifdef _WIN64
    L"crashrpt-x64.dll",
#else
    L"crashrpt-x86.dll",
#endif
    GetApplicationInfo(),
    GetHandlerSettings());

#endif


//---------------------------------------------------------------------------

static int InstallService(const char * sServiceName, const char * sPath) {
	SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	     
	if(schSCManager == NULL) {
	    printf("OpenSCManager failed (%lu)!", GetLastError());
	    return EXIT_FAILURE;
	}
	
	char sBuf[MAX_PATH+1];
	::GetModuleFileName(NULL, sBuf, MAX_PATH);
	
	string sCmdLine = "\"" + string(sBuf) + "\" -s " + string(sServiceName);
	
	if(sPath != NULL) {
	    sCmdLine += " -c " + string(sPath);
	}
	
	SC_HANDLE schService = CreateService(schSCManager, sServiceName, sServiceName, 0, SERVICE_WIN32_OWN_PROCESS,
	    SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, sCmdLine.c_str(),
	    NULL, NULL, NULL, NULL, NULL);
	
	if(schService == NULL) {
	    printf("CreateService failed (%lu)!", GetLastError());
	    CloseServiceHandle(schSCManager);
	    return EXIT_FAILURE;
	} else {
	    printf("PtokaX service '%s' installed successfully.", sServiceName);
	}

	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
	
	return EXIT_SUCCESS;
}
//---------------------------------------------------------------------------
	
static int UninstallService(const char * sServiceName) {
	SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	     
	if(schSCManager == NULL) {
	    printf("OpenSCManager failed (%lu)!", GetLastError());
	    return EXIT_FAILURE;
	}
	
	SC_HANDLE schService = OpenService(schSCManager, sServiceName, SERVICE_QUERY_STATUS | SERVICE_STOP | DELETE);
	     
	if(schService == NULL) {
	    printf("OpenService failed (%lu)!", GetLastError());
	    CloseServiceHandle(schSCManager);
	    return EXIT_FAILURE;
	}
	
	SERVICE_STATUS_PROCESS ssp;
	DWORD dwBytesNeeded;
	
	if(QueryServiceStatusEx(schService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, sizeof(SERVICE_STATUS_PROCESS), &dwBytesNeeded) != 0) {
	    if(ssp.dwCurrentState != SERVICE_STOPPED && ssp.dwCurrentState != SERVICE_STOP_PENDING) {
	        ControlService(schService, SERVICE_CONTROL_STOP, (LPSERVICE_STATUS)&ssp);
	    }
	}
	
	if(DeleteService(schService) == false) {
	    printf("DeleteService failed (%lu)!", GetLastError());
	    CloseServiceHandle(schService);
	    CloseServiceHandle(schSCManager);
	    return EXIT_FAILURE;
	} else {
	    printf("PtokaX service '%s' deleted successfully.", sServiceName);
	    CloseServiceHandle(schService);
	    CloseServiceHandle(schSCManager);
	    return EXIT_SUCCESS;
	}
}
//---------------------------------------------------------------------------
static SERVICE_STATUS_HANDLE ssh;
static SERVICE_STATUS ss;
//---------------------------------------------------------------------------
	
static void WINAPI CtrlHandler(DWORD dwCtrl) {
	switch(dwCtrl) {
	    case SERVICE_CONTROL_SHUTDOWN:
	    case SERVICE_CONTROL_STOP:
	        ss.dwCurrentState = SERVICE_STOP_PENDING;
	        clsServerManager::bIsClose = true;
			clsServerManager::Stop();
	    case SERVICE_CONTROL_INTERROGATE:
	        // Fall through to send current status.
	        break;
	    default:
	        break;
	}
	
	if(SetServiceStatus(ssh, &ss) == false) {
		AppendLog("CtrlHandler::SetServiceStatus failed ("+string((uint32_t)GetLastError())+")!");
	}
}
//---------------------------------------------------------------------------
	
static void WINAPI StartService(DWORD /*argc*/, char* argv[]) {
	ssh = RegisterServiceCtrlHandler(argv[0], CtrlHandler);
	
	if(ssh == NULL) {
		AppendLog("RegisterServiceCtrlHandler failed ("+string((uint32_t)GetLastError())+")!");
	    return;
	}
	
	ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	ss.dwCurrentState = SERVICE_START_PENDING;
	ss.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;
	ss.dwWin32ExitCode = NO_ERROR;
	ss.dwCheckPoint = 0;
	ss.dwWaitHint = 10 * 1000;
	
	if(SetServiceStatus(ssh, &ss) == false) {
		AppendLog("StartService::SetServiceStatus failed ("+string((uint32_t)GetLastError())+")!");
		return;
	}
	
	clsServerManager::Initialize();
	
	if (clsServerManager::Start() == false) {
	    AppendLog("Server start failed!");
		ss.dwCurrentState = SERVICE_STOPPED;
		SetServiceStatus(ssh, &ss);
		return;
	}
	
	ss.dwCurrentState = SERVICE_RUNNING;
	
	if(SetServiceStatus(ssh, &ss) == false) {
		AppendLog("StartService::SetServiceStatus1 failed ("+string((uint32_t)GetLastError())+")!");
		return;
	}

	MSG msg = { 0 };
	BOOL bRet = -1;
	        
	while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
	    if(bRet == -1) {
	        // handle the error and possibly exit
	    } else {
	        if(msg.message == WM_USER+1) {
	            break;
	        } else if(msg.message == WM_TIMER) {
				if (msg.wParam == clsServerManager::sectimer) {
					clsServerManager::OnSecTimer();
				} else if (msg.wParam == clsServiceLoop::srvLoopTimer) {
                    clsServiceLoop::mPtr->Looper();
                } else if(msg.wParam == clsServerManager::regtimer) {
					clsServerManager::OnRegTimer();
                } else {
                    //Must be script timer
                    ScriptOnTimer(msg.wParam);
                }
            }

	    	TranslateMessage(&msg);
	        DispatchMessage(&msg);
	    }
	}

    ExceptionHandlingUnitialize();

	ss.dwCurrentState = SERVICE_STOPPED;
	SetServiceStatus(ssh, &ss);
}
//---------------------------------------------------------------------------

int __cdecl main(int argc, char* argv[]) {
    ::SetDllDirectory("");

#ifndef _WIN64
    HINSTANCE hKernel32 = ::LoadLibrary("Kernel32.dll");

    typedef BOOL (WINAPI * SPDEPP)(DWORD);
    SPDEPP pSPDEPP = (SPDEPP)::GetProcAddress(hKernel32, "SetProcessDEPPolicy");

    if(pSPDEPP != NULL) {
        pSPDEPP(PROCESS_DEP_ENABLE);
    }

    ::FreeLibrary(hKernel32);
#endif

#ifdef _DEBUG
//    AllocConsole();
//    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
//    Cout("PtokaX Debug console\n");
#endif
	
	char sBuf[MAX_PATH+1];
	::GetModuleFileName(NULL, sBuf, MAX_PATH);
	char * sPath = strrchr(sBuf, '\\');
	if(sPath != NULL) {
		clsServerManager::sPath = string(sBuf, sPath - sBuf);
	} else {
		clsServerManager::sPath = sBuf;
	}

	char * sServiceName = NULL;
	
	bool bInstallService = false, bSetup = false;

	for(int i = 1; i < argc; i++) {
	    if(stricmp(argv[i], "-s") == NULL || stricmp(argv[i], "/service") == NULL) {
	    	if(++i == argc) {
	            AppendLog("Missing service name!");
	            return EXIT_FAILURE;
	    	}
	    	sServiceName = argv[i];
			clsServerManager::bService = true;
	    } else if(stricmp(argv[i], "-c") == 0) {
	        if(++i == argc) {
	            printf("Missing config directory!");
	            return EXIT_FAILURE;
	        }
	
	        size_t szLen = strlen(argv[i]);
	        if(szLen >= 1 && argv[i][0] != '\\' && argv[i][0] != '/') {
	            if(szLen < 4 || (argv[i][1] != ':' || (argv[i][2] != '\\' && argv[i][2] != '/'))) {
	                printf("Config directory must be absolute path!");
	                return EXIT_FAILURE;
	            }
	    	}
	
	    	if(argv[i][szLen - 1] == '/' || argv[i][szLen - 1] == '\\') {
				clsServerManager::sPath = string(argv[i], szLen - 1);
	    	} else {
				clsServerManager::sPath = string(argv[i], szLen);
	        }
	    
			if(DirExist(clsServerManager::sPath.c_str()) == false) {
				if(CreateDirectory(clsServerManager::sPath.c_str(), NULL) == 0) {
	                printf("Config directory not exist and can't be created!");
	                return EXIT_FAILURE;
	            }
	        }
	    } else if(stricmp(argv[i], "-i") == NULL || stricmp(argv[i], "/install") == NULL) {
	    	if(++i == argc) {
	            printf("Please specify service name!");
	    		return EXIT_FAILURE;
	    	}
	    	sServiceName = argv[i];
	    	bInstallService = true;
	    } else if(stricmp(argv[i], "-u") == NULL || stricmp(argv[i], "/uninstall") == NULL) {
	    	if(++i == argc) {
	            printf("Please specify service name!");
	    		return EXIT_FAILURE;
	    	}
	    	sServiceName = argv[i];
	    	return UninstallService(sServiceName);
	    } else if(stricmp(argv[i], "-v") == NULL || stricmp(argv[i], "/version") == NULL) {
	    	printf("%s built on %s %s\n", g_sPtokaXTitle, __DATE__, __TIME__);
	    	return EXIT_SUCCESS;
	    } else if(stricmp(argv[i], "-h") == NULL || stricmp(argv[i], "/help") == NULL) {
	        printf("Usage: PtokaX [-v] [-m] [-i servicename] [-u servicename] [-c configdir]\n\n"
				"Options:\n"
				"\t-i servicename\t\t- install ptokax service with given name.\n"
				"\t-u servicename\t\t- uninstall ptokax service with given name.\n"
				"\t-c configdir\t- absolute path to PtokaX configuration directory.\n"
				"\t-v\t\t- show PtokaX version with build date and time.\n"
				"\t-m\t\t- show PtokaX configuration menu.\n"
			);
	    	return EXIT_SUCCESS;
	    } else if(stricmp(argv[i], "/generatexmllanguage") == NULL) {
	        clsLanguageManager::GenerateXmlExample();
	        return EXIT_SUCCESS;
	    } 
		// else if(strcmp(argv[i], "/crash-test-doctor-dump") == NULL)
		// {
		//	crash_test_doctor_dump();
		// }
	    else if(strcasecmp(argv[i], "-m") == 0) {
	    	bSetup = true;
	    } else {
	    	printf("Unknown parameter %s.\nUsage: PtokaX [-v] [-m] [-i servicename] [-u servicename] [-c configdir]\n\n"
				"Options:\n"
				"\t-i servicename\t\t- install ptokax service with given name.\n"
				"\t-u servicename\t\t- uninstall ptokax service with given name.\n"
				"\t-c configdir\t- absolute path to PtokaX configuration directory.\n"
				"\t-v\t\t- show PtokaX version with build date and time.\n"
				"\t-m\t\t- show PtokaX configuration menu.\n",
				argv[i]);
	    	return EXIT_SUCCESS;
		}
	}

	if(bSetup == true) {
		clsServerManager::Initialize();

		clsServerManager::CommandLineSetup();
		
		clsServerManager::FinalClose();

		return EXIT_SUCCESS;
	}

	if(bInstallService == true) {
	    if(sPath == NULL && strcmp(clsServerManager::sPath.c_str(), sBuf) == 0) {
	        return InstallService(sServiceName, NULL);
		} else {
			return InstallService(sServiceName, clsServerManager::sPath.c_str());
		}
	}

    ExceptionHandlingInitialize(clsServerManager::sPath, sBuf);

	if(clsServerManager::bService == false) {
	    clsServerManager::Initialize();
	
	    if(clsServerManager::Start() == false) {
	        printf("Server start failed!");

            ExceptionHandlingUnitialize();

	        return EXIT_FAILURE;
	    } else {
	        printf("%s running...\n", g_sPtokaXTitle);
	    }

	    MSG msg = { 0 };
	    BOOL bRet = -1;

	    while((bRet = ::GetMessage(&msg, NULL, 0, 0)) != 0) {
	        if(bRet == -1) {
	            // handle the error and possibly exit
	        } else {
	            if(msg.message == WM_USER+1) {
	                break;
	            } else if(msg.message == WM_TIMER) {
                    if(msg.wParam == clsServiceLoop::srvLoopTimer) {
                        clsServiceLoop::mPtr->Looper();
                    } else if(msg.wParam == clsServerManager::regtimer) {
                        clsServerManager::OnRegTimer();
                    } else {
                        //Must be script timer
                        ScriptOnTimer(msg.wParam);
                    }
                }
	
	    		::TranslateMessage(&msg);
	            ::DispatchMessage(&msg);
	        }
	    }

        ExceptionHandlingUnitialize();
	} else {
	    SERVICE_TABLE_ENTRY DispatchTable[] = {
	        { sServiceName, StartService },
	        { NULL, NULL }
	    };
	       
	    if(StartServiceCtrlDispatcher(DispatchTable) == false) {
			AppendLog("StartServiceCtrlDispatcher failed ("+string((uint32_t)GetLastError())+")!");

            ExceptionHandlingUnitialize();

	        return EXIT_FAILURE;
	    }
	}

    return EXIT_SUCCESS;
}
//---------------------------------------------------------------------------
